package com.hb.repository;

import com.hb.domain.Spitter;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
 * 使用 Spring Data JPA 可以避免样板式的 Repository 问题（不同的 Repository 只是持久化类不同，持久方法和持久过程基本相同）。我们不再需要一
 * 遍遍地编写相同的 Repository 实现，Spring Data 能够让我们只编写 Repository 接口就可以了。根本就不再需要实现类了。
 * 编写 Spring Data JPA Repository 的关键在于要从一组接口中挑选一个进行扩展。这里，SpitterRepository 扩展了 Spring Data JPA 的 JpaRepository。
 * 通过这种方式，JpaRepository 进行了参数化，所以它就能知道这是一个用来持久化 Spitter 对象的 Repository，并且 Spitter 的 ID 类型为 Long。
 * 另外，它还会继承 18 个执行持久化操作的通用方法，如保存 Spitter、删除 Spitter 以及根据 ID 查询 Spitter。
 * 我们根本不需要编写 SpringDataSpitterRepository 的任何实现类，相反，我们让 Spring Data 来为我们做这件事请。
 *
 * 该接口扩展自 JpaRepository，而 JpaRepository 又扩展自 Repository 标记接口（虽然是间接的）。因此，SpitterRepository 就传递性地扩展了 Repository 接口，
 * 也就是 Repository 扫描时所要查找的接口。当 Spring Data 找到它后，就会创建 SpitterRepository 的实现类，其中包含了继承自 JpaRepository、
 * PagingAndSortingRepository 和 CrudRepository 的 18 个方法。很重要的一点在于 Repository 的实现类是在应用启动的时候生成的，也就是 Spring 的应用上下文创建的时候。
 * 它并不是在构建时通过代码生成技术产生的，也不是接口方法调用时才创建的。
 */
public interface SpringDataSpitterRepository extends JpaRepository<Spitter, Long>, SpitterSweeper {
    /**
     * 使用 Spring Data JPA 规则来定义自定义查询方法，这个方法的实现 Spring Data 会自动帮我们实现，但方法的命名需要符合 Spring Data 的规则。
     * Spring Data JPA 很棒的一点在于它能为 Spitter 对象提供 18 个便利的方法来进行通用的 JPA 操作，而无需你编写任何持久化代码。但是，如果你的需求超过了它所提
     * 供的这 18 个方法的话，该怎么办呢？幸好，Spring Data JPA 提供了几种方式来为 Repository 添加自定义的方法。
     *
     * 当创建 Repository 实现的时候，Spring Data 会检查 Repository 接口的所有方法，解析方法的名称，并基于被持久化的对象来试图推测方法的目的。本质上，
     * Spring Data 定义了一组小型的领域特定语言（domain specific language，DSL），在这里，持久化的细节都是通过 Repository 方法的签名来描述的。Spring Data
     * 能够知道这个方法是要查找 Spitter 的，因为我们使用 Spitter 对 JpaRepository 进行了参数化。方法名 findByUsername 确定该方法需要根据 username 属性相匹配来
     * 查找 Spitter，而 username 是作为参数传递到方法中来的。另外，因为在方法签名中定义了该方法要返回一个 Spitter 对象，而不是一个集合，因此它只会查找一个 username
     * 属性匹配的 Spitter。
     *
     * findByUsername() 方法非常简单，但是 Spring Data 也能处理更加有意思的方法名称。Repository 方法是由一个动词、一个可选的主题（Subject）、关键词 By 以及一个
     * 断言所组成。在 findByUsername() 这个样例中，动词是 find，断言是 Username，主题并没有指定，暗含的主题是 Spitter。作为编写 Repository 方法名称的样例，我们参照名
     * 为 readSpitterByFirstnameOrLastname() 的方法，看一下方法中的各个部分是如何映射的。动词 read，主题 Spitter，关键词 By，断言 FirstnameOrLastname。我们
     * 可以看到，这里的动词是 read，与之前样例中的 find 有所差别。Spring Data 允许在方法名中使用四种动词：get、read、find 和 count。其中，动词 get、read 和 find
     * 是同义的，这三个动词对应的 Repository 方法都会查询数据并返回对象。而动词 count 则会返回匹配对象的数量，而不是对象本身。Repository 方法的主题是可选的。它的主要
     * 目的是让你在命名方法的时候，有更多的灵活性。如果你更愿意将方法称为 readSpittersByFirstnameOrLastname() 而不是 readByFirstnameOrLastname() 的话，那么你
     * 尽可以这么做。对于大部分场景来说，主题会被省略掉。readSpittersByFirstnameOrLastname() 与 readPuppiesByFirstnameOrLastname() 并没有什么差别，它们与
     * readThoseThingsWeWantByFirstnameOrLastname() 同样没有什么区别。要查询的对象类型是通过如何参数化 JpaRepository 接口来确定的，而不是方法名称中的主题。
     * 在省略主题的时候，有一种例外情况。如果主题的名称以 Distinct 开头的话，那么在生成查询的时候会确保所返回结果集中不包含重复记录。断言是方法名称中最为有意思的部分，
     * 它指定了限制结果集的属性。在 readByFirstnameOrLastname() 这个样例中，会通过 firstname 属性或 lastname 属性的值来限制结果。在断言中，会有一个或多个限制结果的条件。
     * 每个条件必须引用一个属性，并且还可以指定一种比较操作。如果省略比较操作符的话，那么这暗指是一种相等比较操作。不过，我们也可以选择其他的比较操作，包括如下的种类：
     * 1、IsAfter、 After、 IsGreaterThan、 GreaterThan
     * 2、IsGreaterThanEqual、 GreaterThanEqual
     * 3、IsBefore、 Before、 IsLessThan、 LessThan
     * 4、IsLessThanEqual、 LessThanEqual
     * 5、IsBetween、 Between
     * 6、IsNull、 Null
     * 7、IsNotNull、 NotNull
     * 8、IsIn、 In
     * 9、IsNotIn、 NotIn
     * 10、IsStartingWith、 StartingWith、 StartsWith
     * 11、IsEndingWith、 EndingWith、 EndsWith
     * 12、IsContaining、 Containing、 Contains
     * 13、IsLike、 Like
     * 14、IsNotLike、 NotLike
     * 15、IsTrue、 True
     * 16、IsFalse、 False
     * 17、Is、 Equals
     * 18、IsNot、 Not
     * 要对比的属性值就是方法的参数。完整的方法签名如下所示：
     * List<Spitter> readByFirstnameOrLastname(String first, String last);
     * 要处理 String 类型的属性时，条件中可能还会包含 IgnoringCase 或 IgnoresCase，这样在执行对比的时候就会不再考虑字符是大写还是小写。例如，要在 firstname
     * 和 lastname 属性上忽略大小写，那么可以将方法签名改成如下的形式：
     * List<Spitter> readByFirstnameIgnoringCaseOrLastnameIgnoresCase(String first, String last);
     * 需要注意，IgnoringCase 和 IgnoresCase 是同义的，你可以随意挑选一个最合适的。作为 IgnoringCase/IgnoresCase 的替代方案，我们还可以在所有条件的后面添
     * 加 AllIgnoringCase 或 AllIgnoresCase，这样它就会忽略所有条件的大小写：
     * List<Spitter> readByFirstnameOrLastnameAllIgnoresCase(String first, String last);
     * 注意，参数的名称是无关紧要的，但是它们的顺序必须要与方法名称中的操作符相匹配。
     * 最后，我们还可以在方法名称的结尾处添加 OrderBy，实现结果集排序。例如，我们可以按照 lastname 属性升序排列结果集：
     * List<Spitter> readByFirstnameOrLastnameOrderByLastnameAsc(String first, String last);
     * 如果要根据多个属性排序的话，只需将其依序添加到 OrderBy 中即可。例如，下面的样例中，首先会根据 lastname 升序排列，然后根
     * 据 firstname 属性降序排列：
     * List<Spitter> readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first, String last);
     * 可以看到，条件部分是通过 And 或者 Or 进行分割的。
     * @param username
     * @return
     */
    Spitter findByUsername(String username);

    /**
     * Spring Data 这个小型的 DSL 依旧有其局限性，有时候通过方法名称表达预期的查询很烦琐，甚至无法实现。如果遇到这种情形的话，
     * Spring Data能够让我们通过 @Query 注解来解决问题。我们依然不需要编写 findAllGmailSpitters() 方法的实现，只需提供
     * 查询即可，让 Spring Data JPA 知道如何实现这个方法。
     * 可以看到，当使用方法命名约定很难表达预期的查询时，@Query 注解能够发挥作用。如果按照命名约定，方法的名称特别长的时候，也
     * 可以使用这个注解。例如，考虑如下的查询方法
     * @return
     */
    @Query("select s from Spitter s where s.email like '%gmail.com'")
    List<Spitter> findAllGmailSpitters();
}
